package com.linzm.core;

import com.linzm.aspect.AbstractAspect;
import com.linzm.aspect.IJoinPoint;
import com.linzm.entry.CheckResult;
import com.linzm.enums.AdviceEnum;
import javafx.util.Pair;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @Date: 2023/11/9 19:18
 * @User linziming
 */
public class BeanInvocationHandler implements InvocationHandler {
    // 目标类
    private Object target;

    // 切面相关信息
    private Pair<Object, CheckResult> aspectResult;

    public BeanInvocationHandler(Object target, Pair<Object, CheckResult> aspectResult) {
        this.target = target;
        this.aspectResult = aspectResult;
    }

    /**
     * 内部类实现环绕通知
     */
    class JoinPoint implements IJoinPoint {
        private final Method method;

        private final Object[] args;

        public JoinPoint(Method method, Object[] args) {
            this.method = method;
            this.args = args;
        }

        @Override
        public Object proceed() {
            try {
                return method.invoke(target, args);
            } catch (IllegalAccessException exception) {
                exception.printStackTrace();
            } catch (InvocationTargetException exception) {
                exception.printStackTrace();
            }
            return null;
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 切面类
        AbstractAspect aspect = (AbstractAspect) aspectResult.getKey();
        // 通知方式
        CheckResult runType = aspectResult.getValue();

        boolean isAround = runType.isRunAround();
        JoinPoint joinPoint = null;
        if (isAround) {
            joinPoint = new JoinPoint(method, args);
        }
        Object result = null;
        try {
            // 1.前置通知
            if (runType.isRunBefore()) {
                runAspectInstance(AdviceEnum.BEFORE, aspect, args);
            }
            // 2.环绕通知
            if (isAround) {
                result = aspect.around(joinPoint);
            } else {
                result = method.invoke(target, args);
            }
            // 3.返回通知
            if (runType.isRunAfterReturning()) {
                runAspectInstance(AdviceEnum.AFTER_RETURNING, aspect, args, result);
            }
            return result;
        } catch (Exception e) {
            // 4.异常通知
            if (runType.isRunAfterThrowing()) {
                runAspectInstance(AdviceEnum.AFTER_THROWING, aspect, args, e);
            }
        } finally {
            // 5.后置通知
            if (runType.isRunAfter()) {
                runAspectInstance(AdviceEnum.AFTER, aspect, args);
            }
        }
        return result;
    }

    private void runAspectInstance(AdviceEnum adviceEnum, AbstractAspect aspect, Object[] args) {
        this.runAspectInstance(adviceEnum, aspect, args, null, null);
    }

    private void runAspectInstance(AdviceEnum adviceEnum, AbstractAspect aspect, Object[] args, Object result) {
        this.runAspectInstance(adviceEnum, aspect, args, null, result);
    }

    private void runAspectInstance(AdviceEnum adviceEnum, AbstractAspect aspect, Object[] args, Throwable e) {
        this.runAspectInstance(adviceEnum, aspect, args, e, null);
    }

    /**
     * 执行切面实例
     *
     * @param adviceEnum adviceEnum
     * @param aspect aspect
     * @param args args
     */
    private void runAspectInstance(AdviceEnum adviceEnum, AbstractAspect aspect, Object[] args, Throwable e,
                                   Object result) {
        try {
            switch (adviceEnum) {
                case BEFORE: {
                    aspect.before();
                    break;
                }
                case AFTER_RETURNING: {
                    aspect.afterReturning(result);
                    break;
                }
                case AFTER_THROWING: {
                    aspect.afterThrowable(e);
                    break;
                }
                case AFTER: {
                    aspect.after();
                    break;
                }
                default: {
                    break;
                }
            }
        } catch (Exception ee) {
            ee.printStackTrace();
        }
    }
}
